#include "myglwidget.h"
#include "scenebasic.h"
#include "MainWindow.h"

#include "glutils.h"
#include <QDebug>
#include <QtGui>
#include "glshadermanager.h"

#include <cstdio>

GLenum g_tex;
int g_res_m = 128;//100;//64;//100;//128;//64;
int g_res_n = 128;//200;//64;//100;//128;//128;
int g_res_l = 128;//200;//64;//200;//128;//128;

int g_LastX = -1;
int g_LastY = -1;
float g_Pan[] = {0.f, 0.f, 0.f};
float g_CamPos[] = {3.f, 0.f, 0.f};
float g_CamAngle[] = {0, 0};
float g_CamDistance = 3.f;
float g_CamRight[] = {0, 0, -1};
float g_CamUp[] = {0, 1, 0};
float g_CamDir[] = {-1, 0, 0};
bool g_LMBPressed = false;
bool g_RMBPressed = false;
bool g_MMBPressed = false;
float g_sourcePos[] = {0, 0, 3};
float g_densityMultiplier = 1.f;
float g_timeStep = 5;

float g_LightPosRad = 3.f;
float g_Light_Phi = 20.f;//0.8f;
float g_Light_Alp = 1.04f;
const float C_PI = 3.1415926535897932384626433832795f;
BOOL g_isDynamicLight = false;

MyGLWidget::MyGLWidget(const QGLFormat & format, QWidget *parent) : QGLWidget(format, parent)
{
    // This tells the widget to accept keyboard focus when the widget is clicked.
    this->setFocusPolicy(Qt::ClickFocus);

    this->setMinimumSize(800,600);

    vRot[0]     = 0.f;  vRot[1]     = 0.f;  vRot[2]     = 0.f;
    vTran[0]    = 0.f;  vTran[1]    = 0.f;  vTran[2]    = -10.f;
	lastPos = QPoint(-1,-1);

	playAnimation();
}

MyGLWidget::~MyGLWidget()
{
    //delete the only instance of glShaderManager
    glShaderManager::deleteInstance();
}

void MyGLWidget::initializeGL() {

    glewExperimental = GL_TRUE;
    GLenum err = glewInit();
    if( GLEW_OK != err )
    {
        qDebug() <<"Error initializing GLEW: " << glewGetErrorString(err) << endl;
    }
    GLUtils::checkForOpenGLError(__FILE__,__LINE__);

    QGLFormat format = this->format();
    qDebug() << "QGLFormat reports profile: ";
    if( format.profile() == QGLFormat::CompatibilityProfile )
        qDebug() << "compatability.\n";
    else if( format.profile() == QGLFormat::CoreProfile )
        qDebug() << "core.\n";
    else
        qDebug() << "none.\n";

    GLUtils::dumpGLInfo();

    glClearColor(0.2f,0.2f,0.2f,1.0f);

	//Create fluid simulator
	float w = 1;
	float h = (float) g_res_n / (float) g_res_m;
	float d = (float) g_res_l / (float) g_res_m;
	fluidSimulator = new FluidGridSimulator(g_res_m, g_res_n, g_res_l, w, h, d);

	//compute light source
	computeLightSource();
}

void MyGLWidget::paintGL() {
	glFinish();
	//Set bounding conditions for pressure and velocity and do one simulation step in the fluid
	fluidSimulator->setVelocityBoundScale(-1);
	fluidSimulator->setPressureBoundScale(1);


	//Set camera matrices, clear color etc.
	glViewport(0, 0, g_width, g_height);
	glClearColor(0, 0, 0, 0);
	glClearDepth(1.f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45, (float)g_width / (float)g_height, 0.01f, 30);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glTranslatef(g_Pan[0], g_Pan[1], g_Pan[2]);
	gluLookAt(g_CamPos[0], g_CamPos[1], g_CamPos[2], 0, 0, 0, 0, 1, 0);

	//render the fluid
	glDepthMask(false);
	fluidSimulator->render(g_width, g_height);
	glDepthMask(true);

	glFinish();

	//Display HUD
	//QString fpsstr;
	//fpsstr.sprintf("%.2f FPS", framesRendered*1000.f/timeElapsed);
	////glDisable(GL_DEPTH_TEST);
	////renderText(100, 100, fpsstr);

	//qDebug() <<"*************************************\n";
	//qDebug() << fpsstr << "\n";
	//qDebug() <<"simulation time: " << fluidSimulator->simulationTime << "\n";
	//qDebug() <<"shading time: " << fluidSimulator->shadingTime << "\n";
	//qDebug() <<"npr time: " << fluidSimulator->nprTime << "\n";
	//qDebug() <<"subsampling time: " << fluidSimulator->subSamplingTime << "\n";
	//qDebug() <<"surface detect time: " << fluidSimulator->surfaceDetectTime << "\n";
}

void MyGLWidget::timerEvent(QTimerEvent *)
{
	//calculate frame time and FPS
	static int lastTime = timeGetTime();
	static int framesRendered = 0;
	int currentTime = timeGetTime();
	framesRendered++;
	int timeElapsed = currentTime - lastTime;

	if (timeElapsed >= 1000)
	{
		framesRendered = 0;
		lastTime = currentTime;
	}


	glFinish();
	int lastT = timeGetTime();
	if (!ispaused)
		fluidSimulator->update();
	glFinish();
	fluidSimulator->simulationTime = timeGetTime() - lastT;
	updateGL();

	QString hud;
	hud.sprintf("fps: %f\nSimulation time: %d\nShading time: %d\nNPR time: %d\nSubsampling time: %d\nSurface detect time: %d\n",
		framesRendered*1000.f/timeElapsed, 
		fluidSimulator->simulationTime,
		fluidSimulator->shadingTime,
		fluidSimulator->nprTime,
		fluidSimulator->subSamplingTime,
		fluidSimulator->surfaceDetectTime
		);

	MainWindow::textEdit->setPlainText(hud);
	qDebug() << fluidSimulator->simulationTime << "\n";
}

void MyGLWidget::resizeGL(int w, int h ) {
	g_width = w/2.f;
	g_height = h;
}

void MyGLWidget::mousePressEvent(QMouseEvent *event)
{
	if (event->button() & Qt::LeftButton)
		g_LMBPressed = true;
	else if (event->button() & Qt::RightButton)
		g_RMBPressed = true;
	else
		g_MMBPressed = true;
}

void MyGLWidget::mouseReleaseEvent(QMouseEvent *event)
{
	lastPos = QPoint(-1,-1);
	if (event->button() & Qt::LeftButton)
		g_LMBPressed = false;
	else if (event->button() & Qt::RightButton)
		g_RMBPressed = false;
	else
		g_MMBPressed = false;
}

void MyGLWidget::mouseMoveEvent(QMouseEvent *event)
{
	////////////////////
	//get cursor movement since last event (dx, dy in pixels)
	int dx;
	int dy;
	if (g_LMBPressed || g_RMBPressed || g_MMBPressed)
	{
		if (lastPos.x() == -1)
		{
			lastPos = event->pos();
			return;
		}
		else
		{
			dx = event->x() - lastPos.x();
			dy = event->y() - lastPos.y();
		}
	}

	lastPos = event->pos();

	//move smoke smoke
	if (event->modifiers() & Qt::ControlModifier || event->modifiers() & Qt::ShiftModifier)
	{
		fluidSimulator->setWindSource(0, dx*0.1f, dy*0.1f, event->modifiers() & Qt::ShiftModifier?0:1);
		if (event->modifiers() & Qt::ShiftModifier)
			fluidSimulator->update();
		updateGL();
		return;
	}

	//panning
	if (g_MMBPressed)
	{
		float distx = (float)dx / 500.f;
		float disty = -(float)dy / 500.f;
		g_Pan[0] += distx;
		g_Pan[1] += disty;
		g_Pan[2] += 0;
		fluidSimulator->setCameraChanged(true);
		updateGL();
		return;
	}

	//zoom
	if (g_RMBPressed)
	{
		g_CamDistance -= (float)dx / 500.f;
		if (g_CamDistance < 0) g_CamDistance = 0;
		g_CamPos[0] = cos(g_CamAngle[0]) * cos(g_CamAngle[1]) * g_CamDistance;
		g_CamPos[2] = sin(g_CamAngle[0]) * cos(g_CamAngle[1]) * g_CamDistance;
		g_CamPos[1] = sin(g_CamAngle[1]) * g_CamDistance;
		fluidSimulator->setCameraChanged(true);
		updateGL();
		return;
	}

	//rotate camera
	if (g_LMBPressed)
	{
		float dCamAngle1 = (float) dx / 500.f;
		float dCamAngle2 = (float) dy / 500.f;

		g_CamAngle[0] += dCamAngle1;
		g_CamAngle[1] += dCamAngle2;
		if (g_CamAngle[1] > 3.14 / 2.f) g_CamAngle[1] = 3.14 / 2.f;
		if (g_CamAngle[1] < -3.14 / 2.f) g_CamAngle[1] = -3.14 / 2.f;

		g_CamPos[0] = cos(g_CamAngle[0]) * cos(g_CamAngle[1]) * g_CamDistance;
		g_CamPos[2] = sin(g_CamAngle[0]) * cos(g_CamAngle[1]) * g_CamDistance;
		g_CamPos[1] = sin(g_CamAngle[1]) * g_CamDistance;

		float lengthCPos = sqrt(g_CamPos[0] * g_CamPos[0] + g_CamPos[1] * g_CamPos[1] + g_CamPos[2] * g_CamPos[2]);
		for(int i = 0; i < 3; i++)
			g_CamDir[i] = -g_CamPos[i] / lengthCPos;

		g_CamRight[0] = -g_CamDir[2];
		g_CamRight[1] = 0;
		g_CamRight[2] = g_CamDir[0];

		g_CamUp[0] = g_CamDir[1] * g_CamRight[2] - g_CamDir[2] * g_CamRight[1];
		g_CamUp[1] = g_CamDir[0] * g_CamRight[2] - g_CamDir[2] * g_CamRight[0];
		g_CamUp[2] = g_CamDir[0] * g_CamRight[1] - g_CamDir[1] * g_CamRight[0];
		fluidSimulator->setCameraChanged(true);
		updateGL();
	}
}

/////////////////////////slot
void MyGLWidget::takeScreenShot() {
	QImage img = this->grabFrameBuffer(true);
	img.save("screen.png", "PNG");
}

void MyGLWidget::changeNPRMode(int id)
{
	switch (id)
	{
	case 0:					//toon1
		fluidSimulator->switchNPRStyle(FluidGridSimulator::TOONSHADE1);
		break;
	case 1:					//toon2
		fluidSimulator->switchNPRStyle(FluidGridSimulator::TOONSHADE2);
		break;
	case 2:					//stippling
		fluidSimulator->switchNPRStyle(FluidGridSimulator::STIPPLING);
		break;
	case 3:					//hatching
		fluidSimulator->switchNPRStyle(FluidGridSimulator::HATCHING);
		break;
	}
	updateGL();
}

void MyGLWidget::changeContourStrokeSize(int range)
{
	fluidSimulator->m_contourtexsize = 100.f*range/200.f;
	updateGL();
}

void MyGLWidget::changeScanlinePaceX(int range)
{
	fluidSimulator->dsampleX = 100.f*range/200.f;
	updateGL();
}

void MyGLWidget::changeScanlinePaceY(int range)
{
	fluidSimulator->dsampleY = 100.f*range/200.f;
	updateGL();
}

void MyGLWidget::changeContourTexture(int id)
{

	fluidSimulator->switchStrokeTexture(id);
	updateGL();
}

void MyGLWidget::changeDmaxValue(int range)
{
	fluidSimulator->m_dMax = range;
	fluidSimulator->genSamplingList();
	updateGL();
}

void MyGLWidget::changeFactorValue(int range)
{
	fluidSimulator->m_factor = 10.f*range/200.f;
	fluidSimulator->genSamplingList();
	updateGL();
}

void MyGLWidget::changePointSize(int range)
{
	fluidSimulator->m_stippleSize = 5.f*range/10.f;
	updateGL();
}

void MyGLWidget::changeHatchStrokeSize(int range)
{
	fluidSimulator->recsize = range/1000.f;
	updateGL();
}

void MyGLWidget::changeHatchTexture(int id)
{
	fluidSimulator->switchHatchTexture(id);
	updateGL();
}

void MyGLWidget::changeKValue(int range)
{
	fluidSimulator->m_Kvalue = 20.f*range/200.f;//range/200.f;
	updateGL();
}

void MyGLWidget::setRenderContour(int flag)
{
	fluidSimulator->isRenderContour = flag;
	updateGL();
}

void MyGLWidget::changeSmoothness(int range)
{
	fluidSimulator->nSmooth = range;
	updateGL();
}

void MyGLWidget::setRenderBounding(int flag)
{
	fluidSimulator->setRenderBoundingCube(flag);
	updateGL();
}

void MyGLWidget::set2Dsmooth(int flag)
{
	fluidSimulator->is2Dsmoothed = flag;
	updateGL();
}

void MyGLWidget::changeAdvectStep(int range)
{
	fluidSimulator->m_advectstep = 10.f*range/200.f;
	updateGL();
}

void MyGLWidget::changeEdgeThres(int range)
{
	fluidSimulator->m_edgethres = range/200.f;
	updateGL();
}

void MyGLWidget::setContourEnhance(int flag)
{
	fluidSimulator->isContourEnhance = flag;
	updateGL();
}

void MyGLWidget::computeLightSource()
{
	//compute new light source position
	fluidSimulator->setLightSource(g_LightPosRad * sin(g_Light_Phi) * cos(g_Light_Alp),
		g_LightPosRad * cos(g_Light_Phi),
		g_LightPosRad * sin(g_Light_Phi) * sin(g_Light_Alp)
		);
}

void MyGLWidget::changeLightAlpha(int range)
{
	g_Light_Alp = range*PI/180.f;
	computeLightSource();
	updateGL();
}

void MyGLWidget::changeLightPhi(int range)
{
	g_Light_Phi = range*PI/180.f;
	computeLightSource();
	updateGL();
}

void MyGLWidget::changeBillboardContour(int range)
{
	fluidSimulator->bbcontourfactor = range/100.f;
	updateGL();
}

void MyGLWidget::changeHatchEdgeThres(int range)
{
	fluidSimulator->m_contourthres = range/200.f;
	updateGL();
}

void MyGLWidget::changeNSlices(int range)
{
	fluidSimulator->m_nslices = range/2.f;
	updateGL();
}

void MyGLWidget::setSubsampling(int flag)
{
	fluidSimulator->isSubSampled = flag;
	updateGL();
}